#/usr/bin/env python
"""
Stores all of the different NPC/PC for the game
"""
import setup
import pygame
import math as m
from pygame.locals import *

#classes for our game objects

# base classes
class Actor(pygame.sprite.Sprite):
    def __init__(self, sprite_name):
        pygame.sprite.Sprite.__init__(self) #call Sprite initializer
        self.image, self.rect = setup.load_image(sprite_name, -1)

    def get_pos(self):
        return self.rect.center


class Mob(Actor):
    """moves a sprite on the screen"""
    def __init__(self, sprite_name):
        Actor.__init__(self, sprite_name)
        self.punching = 0

    def move_abs(self, xy):
        self.rect.center = xy

    def move_inc(self, xy):
        newpos = self.rect.move(xy)
        self.rect = newpos

    def stop(self):
        """ Stop doing what you were doing. """
        pass

# subclasses

class Gob(Mob):
    def __init__(self):
        Actor.__init__(self,'ball.jpg')

    def getLocalOrigin(self):
        orX,orY = self.rect.center
        return (orX,orY)

    def getRadius(self):
        return self.rect.width/2.0

class Player(Mob):
    curr_gob = None
    def __init__(self, gob_start):
        Mob.__init__(self, 'player.png')
        self._gotoGob(gob_start)

    def tick(self):
        self.animate()

    def launch(self):
        """ Fire self away from current gob """
        self.animate = self._animate_fly
        ## don't do this for collision's sake
        #self.curr_gob = None

        gobX,gobY = self.curr_gob.get_pos()
        myX,myY = self.get_pos()

        # get location vector (relative to gob centre)
        x = myX - gobX
        y = myY - gobY

        # Normalize (or something close for now)
        h = m.sqrt(x**2 + y**2)/10.0
        if h == 0:
            h += 0.01
        x = float(x)/h
        y = float(y)/h

        self.velocity = x,y

    def collide(self, gob):
        if self.curr_gob == gob:
            return
        self._gotoGob(gob)

    def _gotoGob(self, gob):
        """ Attach to a gob """
        self.animate = self._animate_revolve
        self.curr_gob = gob

        self.rotation = self._getRotationDegrees_(gob)
        self.move_abs( self._getLocationAtRotation_ForGob_(self.rotation, gob) )

    def _getRotationDegrees_(self, gob):
        """ Returns the location that the player should be at to be at the
            given rotation around the gob. """
        gobX,gobY = gob.getLocalOrigin()
        myX,myY = self.get_pos()
        x = gobX - myX
        y = gobY - myY
        if y == 0:
            y += 0.01
        theta = m.atan( x/y )
        return theta

    def _getLocationAtRotation_ForGob_(self, rot, gob):
        """ Returns the location that the player should be at to be at the
            given rotation around the gob. """
        localX,localY = gob.get_pos()
        h = gob.getRadius()
        y = h * m.cos(rot)
        x = h * m.sin(rot)
        return (x+localX,y+localY)

    # animation functions:
    def _animate_revolve(self):
        self.rotation += 0.07
        self.move_abs( self._getLocationAtRotation_ForGob_(self.rotation, self.curr_gob) )
    def _animate_fly(self):
        self.move_inc( self.velocity )
        

class Fist(Mob):
    """moves a clenched fist on the screen, following the mouse"""
    def __init__(self):
        Mob.__init__(self, 'fist.bmp') #call Mob initializer
        self.punching = 0

    def update(self):
        "move the fist based on the mouse position"
        pos = pygame.mouse.get_pos()
        self.move_abs(pos)
        if self.punching:
            self.rect.move_ip(5, 10)

    def punch(self, target):
        "returns true if the fist collides with the target"
        if not self.punching:
            self.punching = 1
            hitbox = self.rect.inflate(-5, -5)
            return hitbox.colliderect(target.rect)

    def unpunch(self):
        "called to pull the fist back"
        self.punching = 0


class Chimp(Mob):
    """moves a monkey critter across the screen. it can spin the
       monkey when it is punched."""
    def __init__(self):
        Mob.__init__(self, 'chimp.bmp') #call Mob intializer
        screen = pygame.display.get_surface()
        self.area = screen.get_rect()
        self.rect.topleft = 10, 10
        self.move = (9,0)
        self.dizzy = 0

    def update(self):
        "walk or spin, depending on the monkeys state"
        if self.dizzy:
            self._spin()
        else:
            self._walk()

    def _walk(self):
        "move the monkey across the screen, and turn at the ends"
        self.move_inc(self.move)
        if self.rect.left < self.area.left or \
           self.rect.right > self.area.right:
            self.move = -self.move
            self.image = pygame.transform.flip(self.image, 1, 0)

    def _spin(self):
        "spin the monkey image"
        center = self.rect.center
        self.dizzy = self.dizzy + 12
        if self.dizzy >= 360:
            self.dizzy = 0
            self.image = self.original
        else:
            rotate = pygame.transform.rotate
            self.image = rotate(self.original, self.dizzy)
        self.rect = self.image.get_rect(center=center)

    def punched(self):
        "this will cause the monkey to start spinning"
        if not self.dizzy:
            self.dizzy = 1
            self.original = self.image


class Block(Mob):
    """moves a block across the screen. it can spin the
       block when it is punched."""
    def __init__(self):
        Mob.__init__(self, 'S200N801.BMP') #call Mob intializer
        screen = pygame.display.get_surface()
        self.area = screen.get_rect()
        self.rect.topleft = 128,128
        self.speed = 5
        self.original = self.image
        self.dizzy = 0
        self.stop()

    def print_debug(self):
        print "debug:"
        print "self.area=", self.area
        print "self.rect=", self.rect
        print "self.move=", self.move
        print "self.dizzy=", self.dizzy

    def update(self):
        "walk or spin, depending on the monkeys state"
        self._walk()

    def stop(self):
        self.move = (0, 0)

    def up(self):
        "begin moving upwards"
        if self.move[1] < 0:
            return      # same as before
        self.move = (0, -self.speed)

    def down(self):
        "begin moving down"
        if self.move[1] > 0:
            return      # same as before
        self.move = (0, self.speed)

    def right(self):
        "begin moving right"
        if self.move[0] > 0:
            return      # same as before
        self.move = (self.speed,    0)

    def left(self):
        "begin moving left"
        if self.move[0] < 0:
            return      # same as before
        self.move = (-self.speed,    0)

    def _flip_horiz(self):
        "change the direction moved, horizontal axis"
        a,b = self.move
        self.move = -a,b
        self.image = pygame.transform.flip(self.image, 1, 0)

    def _flip_vert(self):
        "change the direction moved, vertical axis"
        a,b = self.move
        self.move = a,-b
        self.image = pygame.transform.flip(self.image, 0, 1)

    def _walk(self):
        "move the monkey across the screen, and turn at the ends"
        if self.rect.top <= self.area.top or self.rect.bottom >= self.area.bottom:
            self._flip_vert()
        elif self.rect.left < self.area.left or self.rect.right > self.area.right:
            self._flip_horiz()
        self.move_inc(self.move)

    def _spin(self):
        "spin the monkey image"
        center = self.rect.center
        self.dizzy = self.dizzy + 12
        if self.dizzy >= 360:
            self.dizzy = 0
            self.image = self.original
        else:
            rotate = pygame.transform.rotate
            self.image = rotate(self.original, self.dizzy)
        self.rect = self.image.get_rect(center=center)

    def _hit(self):
        "this will cause the monkey to start spinning"
        if not self.dizzy:
            self.dizzy = 1
            self.original = self.image


class FollowBlock(Block):
    """moves a block across the screen. it can spin when it is hit"""
    def __init__(self, target):
        Block.__init__(self)
        self.target = target
        self.FIND_PROXIMITY = 128
        self.found_target = 0
        self.speed = 1
        self.path = self._build_path_to_target( self.target.get_pos() )
        print self.path ; print ; print ; print ; print ; print

    def update(self):
        "follow your target"
        self._update_direction(self.target.get_pos())
        if self.found_target:
            self._spin()
        elif self.dizzy > 0:
            self._spin()
        else:
            self._walk()


    def _update_direction_simple(self, targetxy):
        tx,ty = targetxy
        mx,my = self.get_pos()
        xdiff = abs(tx - mx)
        ydiff = abs(ty - my)
        if xdiff > ydiff:
            if tx < mx:
                self.left()
            elif tx > mx:
                self.right()
        elif xdiff < ydiff:
            if ty < my:
                self.up()
            elif ty > my:
                self.down()

#        self.found_target = xdiff < self.FIND_PROXIMITY and ydiff < self.FIND_PROXIMITY

    def _update_direction(self, targetxy):
        tx,ty = targetxy
        mx,my = self.get_pos()
        xdiff = abs(tx - mx)
        ydiff = abs(ty - my)
        self.found_target = xdiff < self.FIND_PROXIMITY and ydiff < self.FIND_PROXIMITY

#        self.path = self._build_path_to_target( self.target.get_pos() )
        try:
            next = self.path[0].pop(1), self.path[1].pop(1)
        except IndexError:
            next = self.target.get_pos()
            self.path = self._build_path_to_target( self.target.get_pos() )
        self._update_direction_simple(next)
        

    def _build_path_to_target(self, target):
        """
        Build the path to the target using Bresenham's line drawing algorithm

        We want to make sure that cmp does what the proper thing would do
        >>> cmp(4,0)
        1
        >>> cmp(-3,0)
        -1
        """
        end = target
        print target, self.get_pos()
        next = [ self.get_pos()[0], self.get_pos()[1] ]
        deltaX = end[0] - next[0]
        deltaY = end[1] - next[1]
        delta = abs(deltaX*2), abs(deltaY*2)

        # set the step in the direction of target
        # this is an optimization (cmp is written in C)
        step = cmp(deltaX, 0), cmp(deltaY, 0)

        # the Bresenham algorithm
        if delta[0] > delta[1]:
            return self._bresenham_helper(0,1, delta, next, step, end)
        else:
            return self._bresenham_helper(1,0, delta, next, step, end)

    def _bresenham_helper(self, U, V, delta, next, step, end):
        """ Assume more movement in U direction. Thus, we can just use the indexes
            to access our elements
        """
        assert delta[U] >= delta[V]


        print (U, V, delta, next, step, end)

        MAX_PATH = 2

        path = [-1]*MAX_PATH, [-1]*MAX_PATH

        fraction = 0
        currentStep = 0

        path[U][currentStep] = next[U]
        path[V][currentStep] = next[V]
        currentStep += 1

        fraction = delta[V]*2 - delta[U]
        try:
            while next[U] != end[U]:
                if fraction >= 0:
                    next[V] += step[V]
                    fraction -= delta[V]
    
                next[U] += step[U]
                fraction += delta[V]
                path[U][currentStep] = next[U]
                path[V][currentStep] = next[V]
                currentStep += 1
        except IndexError:
            # we don't _really_ want to print an error message
            #print "Err:\tTried to build a path longer than MAX_PATH"
            pass

        return path[U][0:currentStep], path[V][0:currentStep]


#this calls the 'main' function when this script is executed
# you should make this run unit tests
if __name__ == '__main__': pass

